// Copyright (c) <2010> <Playdead>
// This file is subject to the MIT License as seen in the trunk of this repository
// Maintained by: <Kristian Kjems> <kristian.kjems+UnitySVN@gmail.com>

// This script uses the SVNIntegration and a local version of the database to issue
// SVN commands. This class does not directly interact with any asynchronously shared
// state.

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using SVNIntegrationNameSpace;
using CommandLineExecution;

namespace SVNIntegrationNameSpace
{
    sealed public class SVNCommands
    {
        private SVNCommands()
        {
            SVNIntegration.instance.SVNDatabaseUpdated += new SVNIntegration.SVNDatabaseHandler(DatabaseEventHandler);
            UpdateSVNStatus();
        }
        ~SVNCommands()
        {
            SVNIntegration.instance.SVNDatabaseUpdated -= new SVNIntegration.SVNDatabaseHandler(DatabaseEventHandler);
        }
        static readonly SVNCommands mInstance = new SVNCommands();
        public static SVNCommands instance { get { return mInstance; } }

        private Dictionary<string, SVNStatus> statusDatabase;
        bool svnBusy = false;

        #region Private methods

        private void DatabaseEventHandler(object source, SVNDatabaseArgs e)
        {
            if (e is SVNDatabaseUpdatedArgs)
            {
                SVNDatabaseUpdatedArgs args = e as SVNDatabaseUpdatedArgs;
                statusDatabase = args.statusDatabase;
                svnBusy = false;
            }
            if (e is SVNDatabaseBusyArgs)
            {
                //SVNDatabaseBusyArgs args = e as SVNDatabaseBusyArgs;
                svnBusy = true;
            }
        }       

        private CommandLineOperation CreateAddOperation(string[] assets, bool async)
        {
            var needToBeAdded =
                from asset in assets
                where statusDatabase[asset].fileStatus == ESVNFileStatus.Unversioned
                select asset;

            if (needToBeAdded.Count() == 0) return null;
            return SVNIntegration.instance.CreateSVNAction("add", async, needToBeAdded.ToArray());
        }

        private CommandLineOperation CreateLockOperation(string[] assets, bool async)
        {
            var needToBelocked =
                from asset in assets
                where
                    statusDatabase[asset].lockStatus == ESVNLockStatus.NoLock &&
                    statusDatabase[asset].fileStatus != ESVNFileStatus.Unversioned
                select asset;

            if (needToBelocked.Count() == 0) return null;
            return SVNIntegration.instance.CreateSVNAction("lock", async, needToBelocked.ToArray());
        }

        private CommandLineOperation CreateUnlockOperation(string[] assets, bool async)
        {
            var needToBeUnlocked =
                from asset in assets
                where
                    statusDatabase[asset].lockStatus == ESVNLockStatus.LockedHere &&
                    statusDatabase[asset].fileStatus != ESVNFileStatus.Unversioned
                select asset;

            if (needToBeUnlocked.Count() == 0) return null;
            return SVNIntegration.instance.CreateSVNAction("unlock", async, needToBeUnlocked.ToArray());
        }


        private void ExecuteSVNAction(CommandLineOperation[] operations)
        {
            var validOperations =
                from op in operations
                where op != null
                select op;

            SVNIntegration.instance.ExecuteSVNAction(validOperations.ToArray());
        }

        private void ExecuteSVNAction(CommandLineOperation operation)
        {
            if (operation != null)
                SVNIntegration.instance.ExecuteSVNAction(operation);
        }

        private void ReleaseHangingFilelocks()
        {
            EditorUtility.UnloadUnusedAssets();
        }

        #endregion

        #region Public methods

        /// <summary>Update SVN status asynchronously</summary>
        public void UpdateSVNStatus()
        {
            if (!svnBusy)
            {
                ExecuteSVNAction(SVNIntegration.instance.asyncSvnStatusOperation);
            }
        }

        public void UpdateSVNStatusSync()
        {
            ExecuteSVNAction(SVNIntegration.instance.syncSvnStatusOperation);
        }

        /// Synchronous SVN status update
        public void UpdateLocalSVNStatus()
        {
            ExecuteSVNAction(SVNIntegration.instance.syncLocalSvnStatusOperation);
        }

        public SVNStatus GetAssetStatus(string assetPath)
        {
            if (statusDatabase == null || (statusDatabase.Count == 0 && !svnBusy))
            {
                UpdateSVNStatus();
            }
            else if (statusDatabase.ContainsKey(assetPath))
            {
                return statusDatabase[assetPath];
            }
            return new SVNStatus();
        }

        public void SVNUpdate()
        {
            ReleaseHangingFilelocks();
            ExecuteSVNAction(new CommandLineOperation[]{
                SVNIntegration.instance.CreateSVNAction("update", true),                
                SVNIntegration.instance.asyncSvnStatusOperation
            });
        }

        public void SVNCommit(string[] assets, string commitMessage)
        {
			
            if (assets.Length > 0)
            {
                ReleaseHangingFilelocks();
                UpdateLocalSVNStatus();
                string command = "commit -m " + "\"" + commitMessage + "\"";

                foreach (string assetPathIt in assets)
                {
                    if (statusDatabase[assetPathIt].bypassRevisionControl)
                    {
                        string errMsg = "Commit Aborted: Tried to commit a file marked as bypass revision control: " + assetPathIt;
                        Debug.LogError(errMsg);
                        return;
                    }
                }

                ExecuteSVNAction(new CommandLineOperation[]{
                    CreateAddOperation(assets, true),                
                    SVNIntegration.instance.CreateSVNAction(command, true, assets),
                    CreateUnlockOperation(assets, true),                
                    SVNIntegration.instance.asyncSvnStatusOperation
                });
            }
        }

        public void SVNAdd(string[] assets)
        {			
            if (assets.Length > 0)
            {
                ReleaseHangingFilelocks();				
                ExecuteSVNAction(SVNIntegration.instance.CreateSVNAction("add", true, assets));
            }
        }

        public void SVNRevert(string[] assets)
        {
		
            if (assets.Length > 0)
            {
                ReleaseHangingFilelocks();
                ExecuteSVNAction(new CommandLineOperation[]{
                    SVNIntegration.instance.CreateSVNAction("revert", true, assets),
                    CreateUnlockOperation(assets, true),
                    SVNIntegration.instance.asyncSvnStatusOperation
                });
                SVNIntegration.instance.SVNBypassRevision(false, assets);
            }
        }

        public void SVNDelete(string[] assets)
        {			
            if (assets.Length > 0)
            {
                ReleaseHangingFilelocks();				
                List<string> svnDeleteAssets = new List<string>();
                foreach (string assetIt in assets)
                {
                    SVNStatus svnStatus;
                    if (statusDatabase.TryGetValue(assetIt, out svnStatus) && svnStatus.fileStatus != ESVNFileStatus.Unversioned)
                    {
                        svnDeleteAssets.Add(assetIt);
                    }
                    else
                    {
                        if (!AssetDatabase.DeleteAsset(assetIt))
                        {
                            System.IO.File.Delete(assetIt);
                        }
                    }
                }
                if (svnDeleteAssets.Count > 0)
                {
                    ExecuteSVNAction(new CommandLineOperation[]{
                        SVNIntegration.instance.CreateSVNAction("delete", true, svnDeleteAssets.ToArray()),                
                        SVNIntegration.instance.asyncSvnStatusOperation
                   });
                }
                else
                {
                    UpdateSVNStatus();
                }
            }
        }

        public void SVNGetLock(string[] assets)
        {
            SVNGetLock(assets, true);
        }

        public void SVNGetLock(string[] assets, bool async)
        {
            if (assets.Length > 0)
            {
                ReleaseHangingFilelocks();
                ExecuteSVNAction(new CommandLineOperation[]{
                    CreateLockOperation(assets, async),                
                    SVNIntegration.instance.syncLocalSvnStatusOperation
                });
            }
        }

        public void SVNReleaseLock(string[] assets)
        {
            SVNReleaseLock(assets, true);
        }

        public void SVNReleaseLock(string[] assets, bool async)
        {
            if (assets.Length > 0)
            {
                ReleaseHangingFilelocks();
                ExecuteSVNAction(new CommandLineOperation[]{
                    CreateUnlockOperation(assets, async),                
                    SVNIntegration.instance.asyncSvnStatusOperation
                });
            }
        }

        public void SVNResolve(string[] assets, bool async, bool theirs)
        {
            if (assets.Length > 0)
            {
                string conflictparameter = theirs ? "--accept theirs-full" : "--accept mine-full";
                ExecuteSVNAction(new CommandLineOperation[]{
                    SVNIntegration.instance.CreateSVNAction("resolve "+conflictparameter, async, assets),                                
                });
            }
        }

        public void SVNBypassRevision(string[] assets)
        {
            SVNIntegration.instance.SVNBypassRevision(assets);
        }

        public void SVNCleanUp()
        {
            SVNIntegration.instance.SVNCleanUp();
        }

        #endregion

    }
}
